"""
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    created by lei.chang on '25/09/2023'
    comment: 主界面
"""
from datetime import datetime as dt, timedelta, datetime
from functools import partial

from PyQt5.QtWidgets import (QMainWindow, QFrame, QLabel, QVBoxLayout, QTabWidget, QWidget,
                             QPushButton, QHBoxLayout, QSplitter,
                             QFileDialog, QMessageBox, QGroupBox, QSizePolicy)
from PyQt5.QtCore import QTimer, Qt

from project.lib.common import okex_client
from project.service.digiccy import DigiccyService
from project.third_part.okex import Okex
from project.lib.common import logger
from project.service.common import CommonService
from project.component.user_lock import LockDialog
from project.component.basic.user_radio_button import UserRadioButton, RadioButton
from project.component.basic.user_combobox import UserCombobox
from project.component.basic.user_text import UserText
from project.component.user_grid_view import UserGridView, Component
from project.component import TITLE_FONT


class MainWindow(QMainWindow):
    """ 主界面
    """
    def __init__(self):
        """ 初始化主窗口
        """
        super().__init__()
        logger.info('主窗体启动...')
        self.time_interval = 0
        self.now = dt.now()
        self.display_date = self.now.date()
        self.data_collection = None
        self.trade = None
        self.annvol = None
        self.frame = None
        self.tab = None
        self.key_manage = None
        # 得到 5M 日K线下一次刷新的时间
        self.next_flush_time = get_next_flush_time(DigiccyService.get_server_time())
        # 自动采集 5M 日K线处理
        self.flush_data_collection_left = None
        self.flush_data_collection_right = None

        CommonService.flush_okex_client()  # 刷新数据采集客户端
        CommonService.flush_okex_trade_client()  # 刷新交易客户端

        # 记录年化率日K线上次导入日期
        self.last_import_date = DigiccyService.get_max_annvol_date()  # 上一次同步年化率数据日期 为了每日00:00开始的年化率数据自动拉取
        self.last_cancel_date = (self.now - timedelta(days=1)).date()  # 上一次撤单、平仓处理日期 为了每日23:55分开始的自动撤单和平仓
        self.user_combobox: UserCombobox = None

        self.init_ui()

        # 2. 创建定时器
        self.timer = QTimer(self)
        self.timer.timeout.connect(partial(TimerEvent.update_time, self=self))  # 更新当前时间
        self.timer.timeout.connect(partial(TimerEvent.flush_date, self=self))  # 信号-槽
        self.timer.timeout.connect(partial(TimerEvent.flush_data_collection, self=self))  # 刷新 5M
        self.timer.timeout.connect(partial(TimerEvent.import_annvol_data, self=self))  # 刷新 年化率数据  00:00
        self.timer.timeout.connect(partial(TimerEvent.cancel_order, self=self))  # 自动撤单平仓 23:55
        self.timer.start(1000)  # 1000 ms = 1 秒

    def init_ui(self):
        """ 初始化UI
        :return:
        """
        self.setWindowTitle('量化自动交易系统')  # 设置窗口标题
        self.setMinimumSize(1200, 900)

        # ---------- 中心 ----------
        central = QWidget()
        self.setCentralWidget(central)

        # ---------- 铺满的 Frame ----------
        self.frame = QFrame()
        self.frame.setFrameShape(QFrame.Box)
        lay_main = QVBoxLayout(central)
        lay_main.setContentsMargins(0, 0, 0, 0)
        lay_main.addWidget(self.frame)

        self.tab = QTabWidget()
        frame_layout = QVBoxLayout(self.frame)
        frame_layout.setContentsMargins(2, 2, 2, 2)
        frame_layout.addWidget(self.tab)

        # 4 个页签
        self.data_collection = self.generate_tab('data_collection')
        self.trade = self.generate_tab('trade')
        self.annvol = self.generate_tab('annvol')
        self.key_manage = self.generate_tab('key_manage')
        self.tab.addTab(self.data_collection, '数据采集')
        self.tab.addTab(self.trade, '交易')
        self.tab.addTab(self.annvol, '年化率')
        self.tab.addTab(self.key_manage, 'Key管理')

    def generate_tab(self, content_txt) -> QWidget:
        """
        :return:
        """
        w = QWidget()
        w.setObjectName(content_txt)
        # 主垂直布局
        vbox = QVBoxLayout(w)
        vbox.setContentsMargins(0, 0, 0, 0)

        # 顶部横条：标题 + 时间
        hbox = QHBoxLayout()
        time_label = QLabel()
        time_label.setObjectName(f'time_{content_txt}')  # 用于样式表/定时器统一刷新
        time_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        time_label.setStyleSheet("font-weight:bold; font-size:12pt;")
        hbox.addStretch()  # 把时间挤到最右
        hbox.addWidget(time_label)
        vbox.addLayout(hbox)

        # 数据采集
        if content_txt == 'data_collection':
            vbox.addWidget(self.generate_data_collection())
        # 交易
        elif content_txt == 'trade':
            vbox.addWidget(self.generate_trade())
        # 年化率
        elif content_txt == 'annvol':
            vbox.addWidget(self.generate_annvol())
        # Key管理
        elif content_txt == 'key_manage':
            vbox.addWidget(self.generate_key_manage())

        vbox.addStretch()

        return w

    def generate_data_collection(self):
        """
        :return:
        """
        # 左边
        splitter = QSplitter(Qt.Horizontal)
        inst_ids = generate_inst_ids(with_blank=False, collection_model='auto')
        inst_ids_auto = Component(
            control=UserCombobox(label_text='自动币种: ',
                                 object_name=f'inst_ids_combobox_auto',
                                 items=inst_ids,
                                 gap=5),
            after_gap=0
        )

        query_button = QPushButton('🔍 查询')
        export_button = QPushButton('📊 导出')
        user_grid_view = UserGridView(
            query_components=[[inst_ids_auto]],
            query_button=query_button,
            export_button=export_button,
            result_height=830
        )
        self.flush_data_collection_left = btn_clicked = partial(DataCollectionPanel.query_data_collection, self=self, user_grid_view=user_grid_view)
        query_button.clicked.connect(btn_clicked)
        btn_clicked = partial(DataCollectionPanel.export_data_collection, self=self, user_grid_view=user_grid_view)
        export_button.clicked.connect(btn_clicked)
        splitter.addWidget(user_grid_view)

        # 右边
        inst_ids = generate_inst_ids(with_blank=True, collection_model='self')
        inst_ids_self = Component(
            control=UserCombobox(label_text='自选币种: ',
                                 object_name=f'inst_ids_combobox_self',
                                 items=inst_ids,
                                 gap=5),
            after_gap=0
        )
        query_button = QPushButton('🔍 查询')
        export_button = QPushButton('📊 导出')
        user_grid_view = UserGridView(
            query_components=[[inst_ids_self]],
            query_button=query_button,
            export_button=export_button,
            result_height=830
        )
        self.flush_data_collection_right = btn_clicked = partial(DataCollectionPanel.query_data_collection, self=self, user_grid_view=user_grid_view)
        query_button.clicked.connect(btn_clicked)
        btn_clicked = partial(DataCollectionPanel.export_data_collection, self=self, user_grid_view=user_grid_view)
        export_button.clicked.connect(btn_clicked)
        splitter.addWidget(user_grid_view)

        # 左右平分
        splitter.setHandleWidth(1)  # 可选：只留视觉线
        splitter.setStretchFactor(0, 1)
        splitter.setStretchFactor(1, 1)

        return splitter

    def generate_annvol(self):
        """ 年化波动率
        :return:
        """
        inst_ids = generate_inst_ids(with_blank=True)
        inst_ids_combobox = Component(
            control=UserCombobox(label_text='币种: ',
                                 object_name=f'inst_ids_combobox',
                                 items=inst_ids,
                                 gap=5),
            after_gap=0
        )

        dates = [(self.display_date - timedelta(days=1 + v)).strftime('%Y-%m-%d') for v in range(14)]
        self.user_combobox = UserCombobox(label_text='日期: ', object_name=f'dates_combobox', items=dates, gap=5)
        dates_combobox = Component(
            control=self.user_combobox,
            after_gap=0
        )

        query_button = QPushButton('🔍 查询')
        export_button = QPushButton('📊 导出')
        user_grid_view = UserGridView(
            query_components=[[inst_ids_combobox, dates_combobox]],
            query_button=query_button,
            export_button=export_button,
            result_height=830
        )

        btn_clicked = partial(AnnvolPanel.query_annvol, user_grid_view=user_grid_view)
        query_button.clicked.connect(btn_clicked)
        btn_clicked = partial(AnnvolPanel.export_annvol, self=self, user_grid_view=user_grid_view)
        export_button.clicked.connect(btn_clicked)

        return user_grid_view

    def generate_trade(self):
        """ 生成交易面板
        :return:
        """
        tab = QTabWidget()

        # 外层页签的两个页面
        tab_algo_order = QWidget()
        vbox = QVBoxLayout(tab_algo_order)
        splitter = QSplitter(Qt.Horizontal)

        left_widget = QWidget()
        left_widget.setContentsMargins(0, 0, 0, 0)
        left_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        left_lay = QVBoxLayout(left_widget)
        algo_auto_group = QGroupBox('基础设置')
        algo_auto_group.setFont(TITLE_FONT)

        v_algo_auto_group = QVBoxLayout()
        h = QHBoxLayout()
        radio_buttons = [
            RadioButton(title='打开', object_name='open', toggle=TradePanel.on_open_toggle),
            RadioButton(title='关闭', object_name='close', toggle=TradePanel.on_close_toggle)
        ]
        h.addWidget(
            UserRadioButton(
                label_text='自动委托:',
                radio_buttons=radio_buttons,
                default_checked=TradePanel.default_checked,
                gap=5
            )
        )
        h.addStretch()
        v_algo_auto_group.addLayout(h)

        # 开仓量
        h = QHBoxLayout()
        user_text = UserText(
            label_text='    开仓量:',
            default_text=TradePanel.get_opening_position_volume(),
            text_align=Qt.AlignRight,
            width=100,
            object_name='opening_position_volume',
            gap=5,
            explain_text='#{text} USDT'
        )
        h.addWidget(user_text)
        h.addStretch()
        submit = QPushButton('📝 修改')
        submit.setFont(TITLE_FONT)
        h.addWidget(submit)
        submit.clicked.connect(partial(TradePanel.update_opening_position_volume, user_text=user_text, parent=self))
        v_algo_auto_group.addLayout(h)

        h = QHBoxLayout()
        user_text = UserText(
            label_text='误差范围:',
            default_text=TradePanel.get_trigger_scope(),
            text_align=Qt.AlignRight,
            width=100,
            object_name='opening_position_volume',
            gap=5,
            explain_text='昨高低挂单价误差<=#{text}%'
        )
        h.addWidget(user_text)
        h.addStretch()
        submit = QPushButton('📝 修改')
        submit.setFont(TITLE_FONT)
        submit.clicked.connect(partial(TradePanel.update_trigger_scope, user_text=user_text, parent=self))
        h.addWidget(submit)
        v_algo_auto_group.addLayout(h)

        algo_auto_group.setLayout(v_algo_auto_group)
        v_algo_auto_group.addStretch()
        left_lay.addWidget(algo_auto_group)
        splitter.addWidget(left_widget)

        right_widget = QWidget()
        right_widget.setContentsMargins(0, 0, 0, 0)
        right_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        v = QVBoxLayout(right_widget)
        inst_ids = generate_inst_ids(with_blank=True)
        inst_ids_combobox = Component(
            control=UserCombobox(label_text='币种: ',
                                 object_name=f'inst_ids_combobox',
                                 items=inst_ids,
                                 gap=5),
            after_gap=0
        )

        query_button = QPushButton('🔍 查询')
        user_grid_view = UserGridView(
            query_components=[[inst_ids_combobox]],
            query_button=query_button,
            result_height=830
        )
        btn_clicked = partial(TradePanel.query_algo_orders, user_grid_view=user_grid_view)
        query_button.clicked.connect(btn_clicked)
        v.addWidget(user_grid_view)
        splitter.addWidget(right_widget)

        # 左右平分
        splitter.setStretchFactor(0, 1)
        splitter.setStretchFactor(1, 1)
        splitter.setSizes([1000, 1000])
        vbox.addWidget(splitter)

        tab.addTab(tab_algo_order, "委托计划")
        risk_control = QWidget()
        tab.addTab(risk_control, "风控面板")

        return tab

    def generate_key_manage(self):
        """
        :return:
        """
        w = QWidget()
        v = QVBoxLayout(w)
        w.setContentsMargins(0, 0, 0, 0)
        w.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        def _generate_hbox(_widget: QWidget=None, before_stretch=False, after_stretch=True, _widgets=None):
            h = QHBoxLayout()
            if before_stretch:
                h.addStretch()
            if _widget:
                h.addWidget(_widget)
            for v in _widgets or []:
                h.addWidget(v)
            if after_stretch:
                h.addStretch()
            return h

        left_group = QGroupBox('数据采集')
        left_group.setFont(TITLE_FONT)
        left_vbox = QVBoxLayout()
        data = DigiccyService.get_config_infos(codes=['api_key_data', 'secret_key_data', 'passphrase_data'])
        api_key_data = UserText(label_text='   Api Key:', gap=0, object_name='api_key_data', default_text=data['api_key_data'], width=300)
        left_vbox.addLayout(_generate_hbox(api_key_data))
        secret_key_data = UserText(label_text='Secret Key:', gap=0, object_name='secret_key_data', default_text=data['secret_key_data'], width=300)
        left_vbox.addLayout(_generate_hbox(secret_key_data))
        passphrase_data = UserText(label_text='Passphrase:', gap=0, object_name='passphrase_data', default_text=data['passphrase_data'], width=300)
        left_vbox.addLayout(_generate_hbox(passphrase_data))

        test = QPushButton('🧪 测试')
        btn_clicked = partial(KeyManagePanel.test_okex_client, self=self, api_key_data=api_key_data, secret_key_data=secret_key_data, passphrase_data=passphrase_data)
        test.clicked.connect(btn_clicked)
        submit = QPushButton('💾 保存')
        btn_clicked = partial(KeyManagePanel.update_okex_client, self=self, api_key_data=api_key_data, secret_key_data=secret_key_data, passphrase_data=passphrase_data)
        submit.clicked.connect(btn_clicked)
        left_vbox.addLayout(_generate_hbox(_widgets=[test, submit], before_stretch=True, after_stretch=False))
        left_group.setLayout(left_vbox)

        right_group = QGroupBox('币种交易')
        right_group.setFont(TITLE_FONT)
        right_vbox = QVBoxLayout()
        radio_buttons = [
            RadioButton(title='实盘', object_name='real', toggle=KeyManagePanel.on_real_toggle),
            RadioButton(title='模拟盘', object_name='simulate', toggle=KeyManagePanel.on_simulate_toggle)
        ]
        right_vbox.addLayout(_generate_hbox(UserRadioButton(label_text='      交易模式:', radio_buttons=radio_buttons, default_checked=KeyManagePanel.default_checked, gap=10)))
        data = DigiccyService.get_config_infos(codes=['api_key_trade', 'secret_key_trade', 'passphrase_trade'])
        api_key_trade = UserText(label_text='   Api Key:', gap=0, object_name='api_key_trade', default_text=data['api_key_trade'], width=300)
        right_vbox.addLayout(_generate_hbox(api_key_trade))
        secret_key_trade = UserText(label_text='Secret Key:', gap=0, object_name='secret_key_trade', default_text=data['secret_key_trade'], width=300)
        right_vbox.addLayout(_generate_hbox(secret_key_trade))
        passphrase_trade = UserText(label_text='Passphrase:', gap=0, object_name='passphrase_trade', default_text=data['passphrase_trade'], width=300)
        right_vbox.addLayout(_generate_hbox(passphrase_trade))

        test = QPushButton('🧪 测试')
        btn_clicked = partial(KeyManagePanel.test_okex_trade_client, self=self, api_key_trade=api_key_trade, secret_key_trade=secret_key_trade, passphrase_trade=passphrase_trade)
        test.clicked.connect(btn_clicked)
        submit = QPushButton('💾 保存')
        btn_clicked = partial(KeyManagePanel.update_okex_trade_client, self=self, api_key_trade=api_key_trade, secret_key_trade=secret_key_trade, passphrase_trade=passphrase_trade)
        submit.clicked.connect(btn_clicked)
        right_vbox.addLayout(_generate_hbox(_widgets=[test, submit], before_stretch=True, after_stretch=False))
        right_group.setLayout(right_vbox)

        splitter = QSplitter(Qt.Horizontal)
        splitter.addWidget(left_group)
        splitter.addWidget(right_group)
        splitter.setStretchFactor(0, 1)
        splitter.setStretchFactor(1, 1)
        splitter.setSizes([1000, 1000])
        v.addWidget(splitter)

        return w

    def closeEvent(self, a0):
        """
        :param a0:
        :return:
        """
        self.timer.stop()
        a0.accept()


def get_five_minute(minute):
    """
    :return:
    """
    five_minute = 0
    for i in range(0, 60, 5):
        """
        """
        if i > minute:
            break
        five_minute = i

    return five_minute


def get_next_flush_time(now):
    """ 取得下一个5分时间点
    :return:
    """
    next_flush_time = now + timedelta(minutes=5)
    next_five_minute = get_five_minute(int(next_flush_time.strftime('%M')))
    next_flush_time = next_flush_time.strftime('%Y-%m-%d %H') + ':' + str(next_five_minute).zfill(2) + ':' + '01'
    next_flush_time = dt.strptime(next_flush_time, '%Y-%m-%d %H:%M:%S')

    return next_flush_time


def generate_inst_ids(with_blank=True, collection_model='self'):
    """
    :return:
    """
    if collection_model == 'auto':
        # todo 暂时无交易逻辑, 写死
        inst_ids = ['BTC-USDT-SWAP']
    else:
        insts = okex_client.get_instruments()
        inst_ids = [v['instId'] for v in insts]
    inst_ids.sort()
    if with_blank:
        inst_ids.insert(0, '')

    return inst_ids


class TimerEvent:
    """ 定时事件
    """
    @staticmethod
    def update_time(self: QMainWindow):
        """
        :return:
        """
        if self.time_interval % 60 == 59:
            now = DigiccyService.get_server_time()  # 同步服务器时间
        else:
            now = dt.now()

        for i in range(self.tab.count()):
            tab_widget = self.tab.widget(i)
            time_label = tab_widget.findChild(QLabel, f'time_{tab_widget.objectName()}')
            if not time_label:
                continue
            time_label.setText(now.strftime('%Y-%m-%d %H:%M:%S'))

        self.now = now

        self.time_interval += 1

    @staticmethod
    def flush_date(self: QMainWindow):
        """
        :return:
        """
        if self.now.date() <= self.display_date:
            return
        self.display_date = dt.now().date()

        self.user_combobox.clear()
        dates = [(self.display_date - timedelta(days=1 + v)).strftime('%Y-%m-%d') for v in range(14)]
        self.user_combobox.add_items(dates)

    @staticmethod
    def flush_data_collection(self: QMainWindow):
        """ 刷新5M日K线
        :return:
        """
        idx = self.tab.currentIndex()
        if idx != 0:
            return

        if self.now.strftime('%Y-%m-%d %H:%M:%S') >= self.next_flush_time.strftime('%Y-%m-%d %H:%M:%S'):
            # 自动采集 5M 线
            self.flush_data_collection_left()  # 自动
            self.flush_data_collection_right()  # 自选
            self.next_flush_time = get_next_flush_time(self.now)
            print(f'下一个刷新时间点: {self.next_flush_time.strftime("%Y-%m-%d %H:%M:%S")}')

    @staticmethod
    def import_annvol_data(self: QMainWindow):
        """
        :return:
        """
        today = self.now.date()
        if (today - self.last_import_date).days > 1:
            dlg = LockDialog(self,
                             window_title='年化率数据采集',
                             label_text='昨日年化率数据缺失，系统正在采集，请稍候...',
                             coroutine_func=partial(DigiccyService.import_annvol_data, date=(today - timedelta(days=1)).strftime('%Y-%m-%d'))
                             )
            dlg.open()

            # 自动挂单
            DigiccyService.auto_algo_order((today - timedelta(days=1)).strftime('%Y-%m-%d'))

            self.last_import_date = today - timedelta(days=1)

    @staticmethod
    def cancel_order(self: QMainWindow):
        """ 自动撤单和平仓
        :param self:
        :return:
        """
        today = self.now.date()
        if today.strftime('%H:%M') < '23:55':
            return

        if (today - self.last_cancel_date).days < 1:
            return

        # 撤单
        DigiccyService.cancel_algo_orders()

        # 平仓
        DigiccyService.close_position()

        self.last_cancel_date = today


class TradePanel:
    """ 交易面板
    """
    @staticmethod
    def default_checked():
        """ radio
        :return:
        """
        object_name = ''
        state = DigiccyService.get_config_info(code='algo_auto_switch')
        if state == 'false':
            object_name = 'close'
        elif state == 'true':
            object_name = 'open'

        return object_name

    @staticmethod
    def on_open_toggle(checked):
        """
        :return:
        """
        if not checked:
            return
        DigiccyService.update_config_info(code='algo_auto_switch', value='true')

    @staticmethod
    def on_close_toggle(checked):
        """
        :param checked:
        :return:
        """
        if not checked:
            return
        DigiccyService.update_config_info(code='algo_auto_switch', value='false')

    @staticmethod
    def get_opening_position_volume():
        """ 取得开仓量
        :return:
        """
        opening_position_volume = DigiccyService.get_config_info(code='opening_position_volume')
        if not opening_position_volume:
            _opening_position_volume = 300
            DigiccyService.update_config_info(code='opening_position_volume', value=opening_position_volume)

        return opening_position_volume

    @staticmethod
    def update_opening_position_volume(user_text: UserText, parent=None):
        """ 修改开仓量
        :return:
        """
        value = user_text.text()
        if not value or int(value) == 0 or int(value) % 100 != 0:
            QMessageBox.information(parent, "错误", f"开仓量必须为100的整数倍")

        # 更新至DB
        DigiccyService.update_config_info(code='opening_position_volume', value=value)

        # 更新user text控件说明
        user_text.flush_explain_text()

        QMessageBox.information(parent, "成功", f"开仓量修改成功")

    # 误差范围
    @staticmethod
    def get_trigger_scope():
        """ 取得误差范围
        :return:
        """
        trigger_scope = DigiccyService.get_config_info(code='trigger_scope')
        if not trigger_scope:
            trigger_scope = 0.01
            DigiccyService.update_config_info(code='trigger_scope', value=trigger_scope)

        return trigger_scope

    @staticmethod
    def update_trigger_scope(user_text: UserText, parent=None):
        """ 修改误差范围
        :param user_text:
        :param parent:
        :return:
        """
        trigger_scope = user_text.text()
        if not trigger_scope or float(trigger_scope) == 0 or float(trigger_scope) * 10000 % 100 != 0:
            QMessageBox.information(parent, "错误", f"开仓量必须为0.01的整数倍")

        # 更新至DB
        DigiccyService.update_config_info(code='trigger_scope', value=trigger_scope)

        # 更新user text控件说明
        user_text.flush_explain_text()

        QMessageBox.information(parent, "成功", f"误差范围修改成功")

    @staticmethod
    def query_algo_orders(user_grid_view: UserGridView):
        """
        :return:
        """
        filters = user_grid_view.get_filter()

        algo_orders = DigiccyService.get_algo_orders(inst_id=filters['inst_ids_combobox'])

        records = CommonService.convert_algo_orders_to_table(algo_orders)

        CommonService.refresh_table(user_grid_view.table, records)


class KeyManagePanel:

    @staticmethod
    def test_okex_client(self: QMainWindow, api_key_data: UserText, secret_key_data: UserText, passphrase_data: UserText):
        """
        :param self:f
        :return:
        """
        print('api_key', api_key_data.text())
        print('api_secret_key', secret_key_data.text())
        print('passphrase', passphrase_data.text())
        _okex_client = Okex(flag='0', certify={'api_key': api_key_data.text(),
                                               'api_secret_key': secret_key_data.text(),
                                               'passphrase': passphrase_data.text()})
        try:
            _okex_client.get_balance()
        except Exception as ex:
            QMessageBox.information(self, "错误", str(ex))
            return False
        QMessageBox.information(self, "成功", '欧易平台对接成功')
        return True

    @staticmethod
    def test_okex_trade_client(self: QMainWindow, api_key_trade: UserText, secret_key_trade: UserText, passphrase_trade: UserText):
        """ 测试交易客户端
        :param self:
        :return:
        """
        trade_mode = DigiccyService.get_config_info(code='trading_mode')
        flag = '0' if trade_mode == 'real' else '1'
        _okex_client = Okex(flag=flag, certify={'api_key': api_key_trade.text(),
                                                'api_secret_key': secret_key_trade.text(),
                                                'passphrase': passphrase_trade.text()})
        try:
            _okex_client.get_balance()
        except Exception as ex:
            QMessageBox.information(self, "错误", str(ex))
            return False
        QMessageBox.information(self, "成功", '欧易平台对接成功')
        return True

    @staticmethod
    def update_okex_client(self: QMainWindow, api_key_data: UserText, secret_key_data: UserText, passphrase_data: UserText):
        """
        :param self:
        :param api_key_data:
        :param secret_key_data:
        :param passphrase_data:
        :return:
        """
        if not api_key_data.text():
            QMessageBox.information(self, '错误', '请输入Api Key信息')
            return
        if not secret_key_data.text():
            QMessageBox.information(self, '错误', '请输入Secret Key信息')
            return
        if not passphrase_data.text():
            QMessageBox.information(self, '错误', '请输入Passphrase信息')
            return

        # 先测试
        if not KeyManagePanel.test_okex_client(self, api_key_data, secret_key_data, passphrase_data):
            return

        # 更新
        DigiccyService.update_config_infos({
            api_key_data: api_key_data.text(),
            secret_key_data: secret_key_data.text(),
            passphrase_data: passphrase_data.text()
        })
        # 刷新数据采集客户端
        CommonService.flush_okex_client()

        QMessageBox.information(self, "成功", "信息保存成功")

    @staticmethod
    def update_okex_trade_client(self: QMainWindow, api_key_trade: UserText, secret_key_trade: UserText, passphrase_trade: UserText):
        """
        :param self:
        :param api_key_trade:
        :param secret_key_trade:
        :param passphrase_trade:
        :return:
        """
        if not api_key_trade.text():
            QMessageBox.information(self, '错误', '请输入Api Key信息')
            return
        if not secret_key_trade.text():
            QMessageBox.information(self, '错误', '请输入Secret Key信息')
            return
        if not passphrase_trade.text():
            QMessageBox.information(self, '错误', '请输入Passphrase信息')
            return

        # 先测试
        if not KeyManagePanel.test_okex_trade_client(self, api_key_trade, secret_key_trade, passphrase_trade):
            return

        # 更新
        DigiccyService.update_config_infos({
            'api_key_trade': api_key_trade.text(),
            'secret_key_trade': secret_key_trade.text(),
            'passphrase_trade': passphrase_trade.text()
        })
        # 刷新数据采集客户端
        CommonService.flush_okex_trade_client()

        QMessageBox.information(self, "成功", "信息保存成功")

    @staticmethod
    def on_real_toggle(checked):
        """ 实盘交易
        :return:
        """
        if not checked:
            return
        DigiccyService.update_config_info(code='trading_mode', value='real')

    @staticmethod
    def on_simulate_toggle(checked):
        """ 虚拟盘交易
        :param checked:
        :return:
        """
        if not checked:
            return
        DigiccyService.update_config_info(code='trading_mode', value='simulate')

    @staticmethod
    def default_checked():
        """ radio
        :return:
        """
        value = DigiccyService.get_config_info(code='trading_mode')

        return value


class DataCollectionPanel:
    """
    """
    @staticmethod
    def query_data_collection(self: QMainWindow, user_grid_view: UserGridView):
        """
        :param self:
        :param user_grid_view:
        :return:
        """
        start_time_str = (self.now - timedelta(days=2)).strftime('%Y-%m-%d') + ' 00:00:00'
        start_time = dt.strptime(start_time_str, '%Y-%m-%d %H:%M:%S')
        end_time = self.now

        filters = user_grid_view.get_filter()
        inst_id = ''
        if 'inst_ids_combobox_auto' in filters:
            inst_id = filters['inst_ids_combobox_auto']
        elif 'inst_ids_combobox_self' in filters:
            inst_id = filters['inst_ids_combobox_self']
        if not inst_id:
            user_grid_view.table.model().clear()
            return

        klines = DigiccyService.get_kline_data_inst(inst_id=inst_id, start_time=start_time, end_time=end_time)
        records = CommonService.convert_klines_to_table_fm(klines)

        CommonService.refresh_table(user_grid_view.table, records)

    @staticmethod
    def export_data_collection(self: QMainWindow, user_grid_view: UserGridView):
        """ 导出所查询的日K线(5M)
        :param self:
        :param user_grid_view:
        :return:
        """
        start_time_str = (self.now - timedelta(days=2)).strftime('%Y-%m-%d') + ' 00:00:00'
        start_time = dt.strptime(start_time_str, '%Y-%m-%d %H:%M:%S')
        end_time = self.now

        filters = user_grid_view.get_filter()
        inst_id = ''
        if 'inst_ids_combobox_auto' in filters:
            inst_id = filters['inst_ids_combobox_auto']
        elif 'inst_ids_combobox_self' in filters:
            inst_id = filters['inst_ids_combobox_self']
        if not inst_id:
            QMessageBox.information(self, "成功", "请选择一个币种")
            return

        default_name = f'5M日K线_{self.dates_combobox.currentText()}.xlsx'
        # 弹出保存对话框
        file_path, _ = QFileDialog.getSaveFileName(
            self,
            "导出文件",
            default_name,
            "Excel 文件 (*.xlsx)"
        )

        if file_path:
            DigiccyService.export_kline_fm(full_path=file_path,
                                           inst_id_auto=filters.get('inst_ids_combobox_auto', ''),
                                           inst_id_self=filters.get('inst_ids_combobox_self', ''),
                                           start_time=start_time,
                                           end_time=end_time)
            QMessageBox.information(self, "成功", f"文件已导出至：\n{file_path}")


class AnnvolPanel:

    @staticmethod
    def query_annvol(user_grid_view: UserGridView):
        """
        :param user_grid_view:
        :return:
        """
        filters = user_grid_view.get_filter()

        records = DigiccyService.get_annvol_data(inst_id=filters['inst_ids_combobox'], date=filters['dates_combobox'])

        # 加载table view
        CommonService.refresh_table(user_grid_view.table, records)

    @staticmethod
    def export_annvol(self: QMainWindow, user_grid_view: UserGridView):
        """ 导出年化波动率
        :return:
        """
        filters = user_grid_view.get_filter()

        default_name = f'年化波动率_{filters["dates_combobox"]}.xlsx'
        # 弹出保存对话框
        file_path, _ = QFileDialog.getSaveFileName(
            self,
            "导出文件",
            default_name,
            "Excel 文件 (*.xlsx)"
        )

        if file_path:
            DigiccyService.export_annvol(date=filters['dates_combobox'],
                                         inst_id=filters['inst_ids_combobox'],
                                         full_path=file_path)
            QMessageBox.information(self, "成功", f"文件已导出至：\n{file_path}")


if __name__ == "__main__":
    """
    """
    # # MainWindow.get_server_time()
    # insts = okex_client.get_instruments()
    # for v in insts:
    #     print(v)

    value = get_five_minute(11)
    print(value)

